package main

import (
	"code.google.com/p/gofpdf"
	"io/ioutil"
	"os"
	"fmt"
	"flag"
)

type pt struct {
	x, y int
}

type diagram struct {
	pdf    *gofpdf.Fpdf
	bx, by float64
	pitch  float64
	n int
	size int
	ri, rj float64
	played map[pt]bool
}

var (
	letters = []string{"A","B","C","D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T"}
)

const (
	font = "courier"
	BLACK = 25
	WHITE = 245
)

func drawBoard(pdf *gofpdf.Fpdf, pitch float64, size int) (d *diagram) {
	pdf.SetFont(font, "", 12)
	d = &diagram{pdf, 0, 0, pitch, 0, size, -1.25, float64(size) + 3, make(map[pt]bool) }

	x, y, _ := pdf.PageSize(0)
	l := float64(size-1) * pitch

	x -= l
	x /= 2

	y=65

	d.bx, d.by = x, y

	pdf.SetDrawColor(0, 0, 0)
	pdf.SetFillColor(0, 0, 0)

	o := float64(0)
	_, vo := d.pdf.GetFontSize()
	vo /= 3

	for i := 0; i < size; i += 1 {
		pdf.Line(x, y+o, x+l, y+o)
		pdf.Line(x+o, y, x+o, y+l)

		label := fmt.Sprint( 19 - i )

		pdf.Text(x - pitch - d.pdf.GetStringWidth( label ), y + o + vo, label)
		pdf.Text(x + l + pitch + pdf.GetStringWidth("19") - d.pdf.GetStringWidth( label ), y + o + vo, label)

		pdf.Text(x + o - pdf.GetStringWidth( letters[i] ) / 2, y - pitch, letters[i])
		pdf.Text(x + o - pdf.GetStringWidth( letters[i] ) / 2, y + l + pitch + 2 * vo, letters[i])

		o += pitch
	}

	hoshi_size := pitch / 12
	if size == 19 {
		pitch *= 6
		for i := float64(0.5); i < 3; i += 1 {
			for j := float64(0.5); j < 3; j += 1 {
				pdf.Circle(x+i*pitch, y+j*pitch, hoshi_size, "FD")
			}
		}
	}

	return
}

func (d *diagram) drawStoneAt(f int, cx, cy float64, label string) {
	d.pdf.SetFillColor(f, f, f)
	if f < 128 {
		d.pdf.SetTextColor(255, 255, 255)
	} else {
		d.pdf.SetTextColor( 0, 0, 0 )
	}

	fs := 11
	d.pdf.SetFont(font, "", float64(fs))
	for d.pdf.GetStringWidth(label) > .9 * d.pitch {
		fs -= 1
		d.pdf.SetFont(font, "", float64(fs))
	}

	_, vo := d.pdf.GetFontSize()
	vo /= 3

	d.pdf.Circle(cx, cy, d.pitch/2.1, "FD")
	d.pdf.Text(cx-d.pdf.GetStringWidth(label)/2, cy+vo, label)
}

func (d *diagram) drawBoardStone(f, sx, sy int, label string) {
	cx, cy := d.bx+float64(sx - 1)*d.pitch, d.by+float64(19 - sy)*d.pitch

	d.drawStoneAt(f, cx, cy, label)
}

func (d *diagram) drawBlackStone(sx, sy int, label string) {
	d.drawBoardStone(BLACK, sx, sy, label)
}

func (d *diagram) drawWhiteStone(sx, sy int, label string) {
	d.drawBoardStone(WHITE, sx, sy, label)
}

func (d *diagram) nextStone() string {
	d.n += 1
	return fmt.Sprint(d.n)
}

func (d *diagram) drawNumberedBlackStone(sx, sy int) {
	d.drawBlackStone(sx, sy, d.nextStone())
}

func (d *diagram) drawNumberedWhiteStone(sx, sy int) {
	d.drawWhiteStone(sx, sy, d.nextStone())
}

func (s *Sgf) tag(tag, def string) string {
	if len(s.tags[tag]) > 0 {
		return s.tags[tag][0]
	}
	return def 
}

func drawTitleBlock( pdf *gofpdf.Fpdf, s *Sgf ) {
	pdf.SetTextColor( 0, 0, 0 )
	pdf.SetFont("times", "B", 14)
	x, _, _ := pdf.PageSize(0)

	title := fmt.Sprint( s.tag("PW", "white"), " ", s.tag("WR",""), " (W) vs. ", s.tag("PB", "black"), " ", s.tag("BR",""), " (B)" )
	pdf.Text( (x - pdf.GetStringWidth( title )) / 2, 25, title )

	pdf.SetFont("times", "", 12)
	title = fmt.Sprint( s.tag("DT",""), "  " )
	pdf.Text( (x - pdf.GetStringWidth( title )) / 2, 35, title )
	title = fmt.Sprint( s.tag("RE",""), " (", len(s.stones), " stones)")
	pdf.Text( (x - pdf.GetStringWidth( title )) / 2, 40, title )
}

func (d *diagram) replayStone( f, x, y int, label string ) {
	d.drawStoneAt(f, d.bx + d.ri * d.pitch, d.by + d.pitch * d.rj, label)
	d.pdf.SetFont(font, "B", 11)
	d.pdf.SetTextColor(0, 0, 0)
	s := fmt.Sprint(letters[x-1], y)
	_, vo := d.pdf.GetFontSize()
	vo /= 3
	d.pdf.Text(d.bx + (d.ri + .75) * d.pitch, d.by + d.pitch * d.rj + vo, s)

	d.ri += 2.5
	if d.ri > float64(d.size) {
		d.ri = -1.25
		d.rj += 1.25
	}
}

func (d*diagram) replayBlackStone( x, y int, label string ) {
	d.replayStone( BLACK, x, y, label );
}

func (d*diagram) replayWhiteStone( x, y int, label string ) {
	d.replayStone( WHITE, x, y, label );
}

func (d*diagram) replayNumberedBlackStone( x, y int ) {
	d.replayBlackStone( x, y, d.nextStone() );
}

func (d*diagram) replayNumberedWhiteStone( x, y int ) {
	d.replayWhiteStone( x, y, d.nextStone()  );
}

func (d *diagram) play( x, y int ) {
	if d.n % 2 == 1 {
		if d.played[pt{x,y}] {
			d.replayNumberedWhiteStone( x, y )
		} else {
			d.drawNumberedWhiteStone( x, y )
		}
	} else {
		if d.played[pt{x,y}] {
			d.replayNumberedBlackStone( x, y )
		} else {
			d.drawNumberedBlackStone( x, y )
		}
	}
	d.played[pt{x,y}] = true
}

type stone struct {
	x, y int
}

type Sgf struct {
	tags map[string][]string
	stones []stone
}

func formatGame(pdf *gofpdf.Fpdf, s *Sgf) {
	pdf.AddPage()

	drawTitleBlock( pdf, s )

	d := drawBoard(pdf, 7, 19)

	for _, stone := range s.stones {
		d.play(stone.x, stone.y)
	}
}

func nodes(record string) (n []string) {
	N, V := 0, []int{0}
	first := true
	intag := false
	for i := 0; i < len(record); {
		if !intag && record[i] == '[' {
			intag = true
		} else if intag && record[i] == ']' && (i == 0 || record[i-1] != '\\' ) {
			intag = false
		}
		if !intag && record[i] == '(' {
			if ! first {
				n = append(n, record[:i])
			}
			record = record[i+1:]
			i = 0
			V = append(V, V[N])
			N += 1
			first = true
		} else if !intag && record[i] == ')' {
			if V[N] == 0 && !first {
				n = append(n, record[:i])
			}
			record = record[i+1:]
			i = 0
			V = V[:N]
			N -= 1
			V[N] += 1
		} else if !intag && V[N] == 0 && record[i] == ';' {
			if ! first {
				n = append(n, record[:i])
			}
			first = false
			record = record[i+1:]
			i = 0
		} else {
			i += 1
		}
	}

	return
}

type tag struct {
	tag, value string
}

func isspace( r uint8 ) bool {
	return r < 0x21
}

func tags( node string) (t []tag) {
	tagging := true
	var t0 tag

	for i := 0; i < len(node); {
		if tagging && node[i] == '[' {
			if i > 0 {
				t0.tag = node[0:i]
			}
			node = node[i+1:]
			i = 0
			tagging = false
		} else if ! tagging && node[i] == ']' && (i < 1 || node[i-1] != '\\') {
			t0.value = node[0:i]
			node = node[i+1:]
			i = 0
			t = append(t, tag{t0.tag, t0.value})
			tagging = true
		} else if tagging && i == 0 && isspace(node[i]) {
			node = node[1:]
		} else {
			i += 1
		}
	}

	return
}

func ordinal(v uint8) int {
	return int(v - 'a' + 1)
}

func parseStone(s string) stone {
	return stone{ ordinal(s[0]), 20 - ordinal(s[1]) }
}

func parse( record string ) (s*Sgf) {
	s = &Sgf{ make(map[string][]string), []stone{} }
	for _, n := range nodes(record) {
		for _, t := range tags(n) {
			if t.tag == "B" || t.tag == "W" {
				s.stones = append(s.stones, parseStone(t.value))
			} else {
				s.tags[t.tag] = append(s.tags[t.tag],t.value)
			}
		}
	}

	return
}

var output = flag.String( "output", "", "output file name")

func main() {
	flag.Parse()
	files := flag.Args()

	var pdf *gofpdf.Fpdf

	if len(files) == 0 {
		if d, e := ioutil.ReadAll( os.Stdin ); e != nil {
			fmt.Println("readall error:", e);
		} else {
			pdf = gofpdf.New("P", "mm", "A4", "font")
			pdf.SetCompression(false)
			formatGame(pdf, parse(string(d)))

			if *output == "" {
				err := pdf.OutputFileAndClose("out.pdf")
				if err == nil {
					fmt.Println("Successfully generated", "out.pdf")
				} else {
					fmt.Println(err)
				}
			}
		}
	} else {
		for _, fn := range files {
			if f, e := os.Open( fn ); e != nil {
				fmt.Println("error opening", fn, ":", e);
			} else if d, e := ioutil.ReadAll( f ); e != nil {
				fmt.Println("readall error:", e);
			} else {
				if *output == "" || pdf == nil {
					pdf = gofpdf.New("P", "mm", "A4", "font")
					pdf.SetCompression(false)
				}

				formatGame(pdf, parse(string(d)))

				if *output == "" {
					of := fn + ".pdf";
					err := pdf.OutputFileAndClose(of)

					if err == nil {
						fmt.Println("Successfully generated", of)
					} else {
						fmt.Println(err)
						return
					}
				}
			}
		}
	}

	if *output != "" {
		err := pdf.OutputFileAndClose(*output)
		if err == nil {
			fmt.Println("Successfully generated", *output)
		} else {
			fmt.Println(err)
		}
	}
}
